package com.coldlz.mmp.proxy.handlerserver;

import com.coldlz.mmp.proxy.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import lombok.extern.slf4j.Slf4j;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;


@Slf4j
public class TryParseProtocolHandler extends ChannelDuplexHandler {
    final UserContext userContext;

    public TryParseProtocolHandler(UserContext userContext) {
        this.userContext = userContext;
    }


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception {
        Protocol protocolName = userContext.getProxyInfo().getProtocolName();
        if (protocolName != null) {
            ChannelPipeline pipeline = ctx.pipeline();
            if (protocolName == Protocol.HTTP) {
                Util.configHttpPipeline(userContext, ctx, this);// http
                pipeline.fireChannelRead(obj);
            } else {
                Util.removeChannelHandler(ctx, this);
                pipeline.fireChannelRead(obj);
            }
            return;
        }
        if (!(obj instanceof ByteBuf msg)) {
            throw new RuntimeException("参数异常,当前handler只能处理 ByteBuf 类型");
        }
        ctx.pipeline().addLast(new TryPlaintextParseHandler(userContext));
        ctx.fireChannelRead(msg);
        Util.removeChannelHandler(ctx, this);
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        log.error("解析协议发生异常，将关闭连接");
        ctx.close();
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        super.write(ctx, msg, promise);
    }

    private abstract static class TryParseHandler extends ChannelInboundHandlerAdapter {
        final UserContext userContext;

        private TryParseHandler(UserContext userContext) {
            this.userContext = userContext;
        }

        @Override
        public final void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception {
            if (userContext.getProxyInfo().getProtocolName() != null) {
                // 如果已经解析出来了过了那么就直接跳过本handler解析逻辑
                Util.removeChannelHandler(ctx, this);
                ctx.pipeline().fireChannelRead(obj);
                return;
            }
            if (!(obj instanceof ByteBuf msg)) {
                throw new RuntimeException("参数异常,当前handler只能处理 ByteBuf 类型");
            }
            int len = msg.readableBytes();
            if (len == 0) {
                return;
            }
            int startIndex = msg.readerIndex();
            byte[] contentBytes = new byte[len];
            msg.readBytes(contentBytes);
            this.channelReadSub(ctx, msg, startIndex, contentBytes);
        }

        protected abstract void channelReadSub(ChannelHandlerContext ctx, ByteBuf msg, int startIndex, byte[] contentBytes);
    }

    private static class TrySslAfterParseHandler extends TryParseHandler {

        public TrySslAfterParseHandler(UserContext userContext) {
            super(userContext);
        }

        @Override
        protected void channelReadSub(ChannelHandlerContext ctx, ByteBuf msg, int startIndex, byte[] contentBytes) {
            ChannelPipeline pipeline = ctx.pipeline();

            String content = new String(contentBytes, Charset.defaultCharset());
            String[] contents = content.split(Constant.httpHeaderNewLine);

            ProxyInfo info = userContext.getProxyInfo();
            Protocol protocol = Util.wssOrHttps(contents);
            info.setProtocolName(protocol);

            if (protocol == Protocol.WSS) {
                Util.configWsPipeline(userContext, ctx, this, contents, info, contentBytes);// wss

                msg.readerIndex(startIndex);
                pipeline.fireChannelRead(msg);
            } else if (protocol == Protocol.HTTPS) {
                Util.configHttpPipeline(userContext, ctx, this);// https

                msg.readerIndex(startIndex);
                ChannelHandlerContext sslCtx = pipeline.context(SslHandler.class);
                sslCtx.fireChannelRead(msg);
            } else {
                throw new RuntimeException("参数异常");
            }
        }
    }


    private static class TryPlaintextParseHandler extends TryParseHandler {

        public TryPlaintextParseHandler(UserContext userContext) {
            super(userContext);
        }

        @Override
        protected void channelReadSub(ChannelHandlerContext ctx, ByteBuf msg, int startIndex, byte[] contentBytes) {
            ChannelPipeline pipeline = ctx.pipeline();

            ProxyInfo info = userContext.getProxyInfo();
            String content = new String(contentBytes, Charset.defaultCharset());
            String[] contents = content.split(Constant.httpHeaderNewLine);
            if (isPlaintextReq(contentBytes)) {
                info.setProtocolName(Protocol.WS);// 这里必定是ws，因为如果是http协议的话在前面就已经解析好了
                Util.configWsPipeline(userContext, ctx, this, contents, info, contentBytes);// ws

                msg.readerIndex(startIndex);
                pipeline.fireChannelRead(msg);
                return;
            }

            SslContext serverSsl = SslContextManager.getServerSsl(info.getHost());
            SslHandler sslHandler = serverSsl.newHandler(ctx.alloc());
            pipeline.addLast(sslHandler);
            pipeline.addLast(new TrySslAfterParseHandler(userContext));
            Util.removeChannelHandler(ctx, this);

            msg.readerIndex(startIndex);
            pipeline.fireChannelRead(msg);
        }

        private boolean isPlaintextReq(byte[] contentBytes) {
            if (contentBytes.length <= Util.maxLen) {
                return false;
            }
            byte[][] methods = Util.METHODS;
            for (byte[] method : methods) {
                boolean isStartWith = byteStartWith(contentBytes, method);
                if (isStartWith) {
                    return true;
                }
            }
            return false;
        }

        private boolean byteStartWith(byte[] contentBytes, byte[] method) {
            for (int i = 0; i < method.length; i++) {
                if (contentBytes[i] == method[i]) {
                    continue;
                }
                return false;
            }
            return true;
        }
    }

    private static class Util {

        public static final byte[] CONNECT = "CONNECT".getBytes(StandardCharsets.UTF_8);
        public static final byte[] GET = "GET".getBytes(StandardCharsets.UTF_8);
        public static final byte[] POST = "POST".getBytes(StandardCharsets.UTF_8);
        public static final byte[] PUT = "PUT".getBytes(StandardCharsets.UTF_8);
        public static final byte[] DELETE = "DELETE".getBytes(StandardCharsets.UTF_8);
        public static final byte[] OPTIONS = "OPTIONS".getBytes(StandardCharsets.UTF_8);
        public static final byte[] HEAD = "HEAD".getBytes(StandardCharsets.UTF_8);
        public static final byte[] TRACE = "TRACE".getBytes(StandardCharsets.UTF_8);
        public static final byte[] PATCH = "PATCH".getBytes(StandardCharsets.UTF_8);

        public static final byte[][] METHODS = new byte[][]{
                CONNECT, GET, POST, PUT, DELETE, OPTIONS, HEAD, TRACE, PATCH
        };
        public static int maxLen = -1;

        static {
            for (byte[] method : METHODS) {
                maxLen = Math.max(method.length, maxLen);
            }
        }

        private static void removeChannelHandler(ChannelHandlerContext ctx, ChannelHandler channelHandler) {
            if (!ctx.isRemoved()) {
                ctx.pipeline().remove(channelHandler);
            }
        }

        private static WebSocketVersion parseWebSocketVersion(String[] contents) {
            for (String content : contents) {
                if (content.startsWith("Sec-WebSocket-Version: ")) {
                    String versionStr = content.split(":")[1];
                    versionStr = versionStr.trim();
                    return switch (versionStr) {
                        case "13" -> WebSocketVersion.V13;
                        case "8", "08" -> WebSocketVersion.V08;
                        case "7", "07" -> WebSocketVersion.V07;
                        case "0", "00" -> WebSocketVersion.V00;
                        default -> WebSocketVersion.UNKNOWN;
                    };
                }
            }
            log.warn("解析webSocket版本失败");
            return WebSocketVersion.UNKNOWN;
        }

        private static void sendHandshakeReq(RemoteClient client) {
            ChannelPipeline pipeline = client.getRemoteChannel().pipeline();
            ChannelHandlerContext handshakeContext = null;
            ChannelHandler handshakeHandler = null;
            while (true) {
                // 等待 RemoteClient 初始化pipeline完毕
                handshakeContext = pipeline.context("io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandshakeHandler");
                handshakeHandler = pipeline.get("io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandshakeHandler");
                if (handshakeContext != null && handshakeHandler != null) {
                    break;
                }
                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
            }
            // 由于在前期connect的时候还不知道需要哪些handler，因此此时的pipeline是没有handler的
            // 所以这里需要重新触发一次 active 函数，使其进行ws协议的握手操作
            pipeline.firstContext().fireChannelActive();
        }

        private static Protocol wssOrHttps(String[] contents) {
            for (String cnt : contents) {
                if (cnt.equalsIgnoreCase("Upgrade: websocket")) {
                    return Protocol.WSS;
                }
            }
            return Protocol.HTTPS;
        }

        private static void configWsPipeline(UserContext userContext, ChannelHandlerContext ctx, ChannelHandler self
                , String[] contents, ProxyInfo info, byte[] contentBytes) {
            ChannelPipeline pipeline = ctx.pipeline();

            URI webSocketUri;
            try {
                String uriStr = contents[0].split(" ")[1];
                webSocketUri = new URI(uriStr);
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }

            WebSocketVersion webSocketVersion = Util.parseWebSocketVersion(contents);
            info.setWebSocketURI(webSocketUri);
            info.setWebSocketVersion(webSocketVersion);
            info.setHandshakeInfo(contentBytes);

            pipeline.addLast(Constant.httpServerCodecName, new HttpServerCodec());
            pipeline.addLast(new PeekMessageHandler(userContext));
            pipeline.addLast(new WebSocketServerProtocolHandler(webSocketUri.toString()));
            pipeline.addLast(new ServerProxyWsHandler(userContext));
            Util.removeChannelHandler(ctx, self);

            List<Header> headers = new ArrayList<>(contents.length-1);
            if (contents.length > 1) {
                for (int i = 1; i < contents.length; i++) {
                    String str = contents[i];
                    int j = str.indexOf(Constant.httpHeaderSplitter);
                    if (j < 0) {
                        continue;
                    }
                    String k = str.substring(0, j);
                    String v = str.substring(j + 1);
                    headers.add(new Header(k, v.trim()));
                }
            }
            info.setWebSocketHttpHeaders(headers);
            // 重新配置 remoteClient 的 pipeline 信息
            RemoteClient client = RemoteClientFactory.getClient(userContext);
            reinitializeRemoteClientChannel(client);
            Util.sendHandshakeReq(client);
        }

        private static void reinitializeRemoteClientChannel(RemoteClient client) {
            client.getClientChannelInitializer().reinitializeChannel(client.getRemoteChannel());
        }

        private static void configHttpPipeline(UserContext userContext, ChannelHandlerContext ctx, ChannelHandler self) {
            ChannelPipeline pipeline = ctx.pipeline();
            pipeline.addLast(Constant.httpServerCodecName, new HttpServerCodec());
            pipeline.addLast(new PeekMessageHandler(userContext));
            pipeline.addLast(new ServerProxyHttpHandler(userContext));
            Util.removeChannelHandler(ctx, self);

            RemoteClient client = RemoteClientFactory.getClient(userContext);
            reinitializeRemoteClientChannel(client);
        }
    }
}
